Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

646 use graph model with ABMs v3 #1085

Open
wants to merge 107 commits into
base: main
Choose a base branch
from

Conversation

jubicker
Copy link
Member

@jubicker jubicker commented Aug 1, 2024

Changes and Information

Please briefly list the changes (main added features, changed items, or corrected bugs) made:

  • add node property and edge property class for ABM graph simulation
  • add model wrapper for abm that is used for ABM graph simulation and can handle persons that commute to other nodes
  • add tests for new functionality
  • add graph abm example

Merge Request - Guideline Checklist

Please check our git workflow. Use the draft feature if the Pull Request is not yet ready to review.

Checks by code author

  • Every addressed issue is linked (use the "Closes #ISSUE" keyword below)
  • New code adheres to coding guidelines
  • No large data files have been added (files should in sum not exceed 100 KB, avoid PDFs, Word docs, etc.)
  • Tests are added for new functionality and a local test run was successful (with and without OpenMP)
  • Appropriate documentation for new functionality has been added (Doxygen in the code and Markdown files if necessary)
  • Proper attention to licenses, especially no new third-party software with conflicting license has been added
  • (For ABM development) Checked benchmark results and ran and posted a local test above from before and after development to ensure performance is monitored.

Checks by code reviewer(s)

  • Corresponding issue(s) is/are linked and addressed
  • Code is clean of development artifacts (no deactivated or commented code lines, no debugging printouts, etc.)
  • Appropriate unit tests have been added, CI passes, code coverage and performance is acceptable (did not decrease)
  • No large data files added in the whole history of commits(files should in sum not exceed 100 KB, avoid PDFs, Word docs, etc.)
  • On merge, add 2-5 lines with the changes (main added features, changed items, or corrected bugs) to the merge-commit-message. This can be taken from the briefly-list-the-changes above (best case) or the separate commit messages (worst case).

@jubicker jubicker linked an issue Aug 1, 2024 that may be closed by this pull request
2 tasks
Copy link
Member

@DavidKerkmann DavidKerkmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the sophisticated implementation!
In addition to a couple of small things, we should have a more detailed look at two things:

  1. History embedding in ABM graph simulation
  2. RNG embedding in ABM graph simulation
    Details can be found in the comments.

cpp/models/graph_abm/graph_abm_mobility.h Outdated Show resolved Hide resolved
Comment on lines 137 to 149
model1.parameters.get<mio::abm::IncubationPeriod>() = 4.;
model1.parameters.get<mio::abm::InfectedNoSymptomsToSymptoms>() = 2.;
model1.parameters.get<mio::abm::InfectedNoSymptomsToRecovered>() = 4.;
model1.parameters.get<mio::abm::InfectedSymptomsToRecovered>() = 5.;
model1.parameters.get<mio::abm::InfectedSymptomsToSevere>() = 6.;
model1.parameters.get<mio::abm::SevereToRecovered>() = 8.;
model1.parameters.get<mio::abm::SevereToCritical>() = 7.;
model1.parameters.get<mio::abm::CriticalToRecovered>() = 10.;
model1.parameters.get<mio::abm::CriticalToDead>() = 11.;

//Age group 0 goes to school and age group 1 goes to work
model1.parameters.get<mio::abm::AgeGroupGotoSchool>()[age_group_children] = true;
model1.parameters.get<mio::abm::AgeGroupGotoWork>()[age_group_adults] = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these are supposed to be for model2?

{
}

/// @brief Create an invalid ID.
PersonId()
: mio::TypeSafe<uint32_t, PersonId>(std::numeric_limits<uint32_t>::max())
: mio::TypeSafe<uint64_t, PersonId>(std::numeric_limits<uint32_t>::max())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We quickly talked about the change to 64 bit.
A comment somewhere here would be good to explain that only valid uint32_t values should be used for the Id. Perhaps even an assert in the constructor?

}

private:
Sim m_simulation; ///< ABM Simulation of the node
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a comment:
This is interesting. I would have thought that each node is the Model, not holding a simulation, and that there is only one simulation that determines how to advance globally.
Holding a simulation for each node however allows for (potential) different time steps, and it is in line with the ODE models. (And allows for refined histories, individual results, etc.)

mio::abm::add_exposure_contribution(m_air_exposure_rates_cache[location],
m_contact_exposure_rates_cache[location], person,
get_location(person.get_id()), t, dt);
if (person.get_location_model_id() == m_id) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also check with the activeness status here to be consistent?

* @param[in] node ABMSimulationNode to which the functor is applied.
*/
template <class... History>
void evolve_model(abm::TimePoint t, abm::TimeSpan dt, ABMSimulationNode<History...>& node)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See naming comment above. Perhaps: advance_node?

Comment on lines +39 to +46
struct MockHistory {

template <class T>
void log(const T& t)
{
mio::unused(t);
}
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be omitted if the changes for the history are accepted.
Perhaps one needs to test the alternative advance with a history, though.

Comment on lines +154 to +155
graph.add_edge(0, 1);
graph.add_edge(1, 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the Ids be used here, too?

EXPECT_EQ(node2.get_simulation().get_model().get_persons().size(), 0);
EXPECT_EQ(node1.get_simulation().get_model().get_persons().size(), 4);
EXPECT_EQ(node1.get_simulation().get_model().get_activeness_statuses()[p2_index], false);
EXPECT_EQ(node1.get_simulation().get_model().get_activeness_statuses()[p4_index], false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One could also check the buffer sizes at this point, perhaps not 100% needed, you can decide.

namespace mio
{
using namespace abm;
class ModelWrapper : public abm::Model
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit unhappy with the naming, as writing ModelWrapper in the graph initialization is not so nice in my opinion. I'd like this to also be called Model, but of course this is a problem, unless we use a different namespace here. Maybe we can discuss this. Options:

  1. Keep the name
  2. Rename namespace to abm_graph or similar
  3. Rename to Model and rename abm::Model to something else

Comment on lines 65 to 71
Person::Person(const Person& other, PersonId id, uint64_t unique_id)
: Person(other)
{
m_person_id = id;
m_unique_id = unique_id;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we have now two IDs, one of which does not always ID(entify) a person? This needs at least a change in naming or documentation.

As an idea, maybe use local/global in the name of the ID?
Maybe than it could make sense that the global ID defaults to INVALID_ID. And when it is invalid, get_global_id can fall back to the local ID, making them "the same" automatically?

Copy link
Member Author

@jubicker jubicker Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a change in naming is a good idea, we can discuss that.

To the second part: If I understand you right, this is exactly what is done in "add_person".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of... But I think we should decide either on the World always overwriting the unique ID (like it does with PersonID), or on the World not changing it at all. I feel like changing the ID only when it is invalid can lead to unexpected behaviour, as invalid now means "not set, please overwrite" instead.

Copy link
Member Author

@jubicker jubicker Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot always overwrite it, because when a person changes the world, then it would lose its unique id. What we need to do is only set it, if it has not been set before. I did this using the INVALID_UNIQUE_ID, but I'm open for other solutions :).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the global ID is meant to be set only once, when the person first enters the simulations and is assigned a "home world". For me, this changes make sense. Best would be to have the global ID const, which is initialized during the Person constructor, but this would change the add_person structure. We could make some struct that only allows for a one-time modification and then being const. But there might be better solutions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we need to do is only set it, if it has not been set before. I did this using the INVALID_UNIQUE_ID

This works now, but the reason we have this invalid id is to signal that something went wrong, hence all the asserts checking for it. If you use it additionally as a "not set" flag, this can potentially end up hiding other problems.

I would be in favor of making it effectively const - an actually const member would be annoying, as we cannot use default move or copy ctors.
Some options would be:

  • Setting the UID in main, and removing the UID overwrite from add_person and that Person ctor which copies a Person and assigns a new ID
  • using an optional (so we have a "not set" state)
  • defining a second value like invalid_id
    The latter two options then require checking that the id is both set and valid every time we pass it to a new function. The first requires additional setup outside of the model (which is okay in theory, as it is a "global" id).

All are a bit weird in that we treat the IDs differently. Only one is always being overwritten by the world, the other only sometimes, or elsewhere.

@DavidKerkmann DavidKerkmann changed the title 646 use graph model with abms v3 646 use graph model with ABMs v3 Jan 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
class::feature A feature to be implemented for some part of the software loc::backend This issue concerns the C++ backend implementation. model::abm This issue concerns any kind of agent-based model.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use graph model with abms
3 participants